//valid
;(function ($, window, undefined) {
  $.noop = $.noop || function () {};
  var list = {},
      ns = "valid",
      nsname = "core-plugins-"+ ns.toLowerCase();
  var zmvalid = function(config){
    if(typeof config === "string"){
      // Methods
      var rets = [], args = arguments;
      this.each(function(){
        var id = $(this).attr(nsname);
        if(id && list[id]) rets.push(list[id]._api.apply(list[id], args));
      });
      return rets.length > 0 ? rets[0] : null;
    } else {
      // Init
      config = config || {};
      for (var i in zmvalid.defaults) if(config[i] === undefined) config[i] = zmvalid.defaults[i];
      return this.each(function(){
        var id = Math.random();
        !$(this).attr(nsname) && $(this).attr(nsname, id) && (list[id] = new zmvalid.fn._init(this, id, config));
      });
    }
  };
  zmvalid.fn = zmvalid.prototype = {
    version: '1.0.0',
    _init: function (element, id, config) {
      var that = this;
      that.element = element;
      that.id = id;
      that.config = config;
      that.config.scan = that.config.scan || function(data) {
        if (!data.valid) {
          $.each(data.result, function(i, n) {
            !n.valid && n.element.trigger("highlight", true);
          });
          data.firstError.element.triggerHandler("focus");
        } else {
          this.find(".text-error").removeClass("text-error");
        }
      };
      that.__init.call(that);
      config.init && config.init.call(that);
      return that;
    },
    _api: function(){
      var that = this, args = [];
      for(var i=0; i<arguments.length; i++) args.push(arguments[i]);
      if(args.length === 0 || that[args[0]] === undefined) return that;
      if(typeof that[args[0]] === "function") return that[args[0]].apply(that, args.slice(1));
      return that;
    },
    __init: function(){
      var that = this, $element = $(that.element), $form = $element;
      if(!$element.is("[validate-rules]")) $element = $element.find("[validate-rules]");
      that.config.type && that.config.type.callback && $element.each(function(){
        var $this = $(this), _type = $this.attr("validate-type");
        if($this.not(':disabled')){
          if(!_type) _type = that.config.type.name;
          _type && $this.bind(_type + "." + ns, function(event){
            that.config.type.callback && that.config.type.callback.call($this, that.validate($this)[0]);
          });
        }
      });
      $form.is("form") && $form.bind("submit", function(event){
        if(!!that.config.beforeSuccess){
          if(typeof that.config.beforeSuccess == 'function'){
            that.config.beforeSuccess.call($form);
          }
        }
        if(!that.scan().valid){
          event.preventDefault();
          return false;
        } else {
          if(that.config.success){
            if(typeof that.config.success == 'function'){
              if(!that.config.success.call($form)){
                event.preventDefault();
                return false;
              }
            }
          } else {
            event.preventDefault();
            return false;
          }
        }
      });
      if($form.is("form") && (navigator.userAgent.toUpperCase().indexOf("FIREFOX")?true:false)){
        var flag = false;
        $form.find('[validate-rules]').each(function(){
          ($(this).attr('validate-rules').indexOf('\'ajax\'')!=-1) && (flag = true);
        });
        if(flag){
          $form.find('[type="submit"]').bind('mousedown',function(){
            $(this).trigger('click');
          });
        }
      }
      return that;
    },
    _ruleCompile: function(_element, _rules, _validity){
      var that = this, _value = $(_element).val();
      _rules = _rules || [];
      if(typeof _rules == "string") _rules = [_rules];
      for(var i=0; i<_rules.length; i++){
        var _rule = _rules[i], rule = { name: "", extend: null, message: "" };
        if(typeof _rule == "string") {
          rule.name = _rule;
        } else if(Object.prototype.toString.call(_rule) === "[object Array]") {
          rule.name = _rule[0];
          if(_rule.length === 2){
            if(typeof _rule[1] == "object"){
              rule.extend = _rule[1];
            } else if(typeof _rule[1] == "string"){
              rule.message = _rule[1];
            }
          } else if(_rule.length === 3){
            rule.extend = _rule[1];
            rule.message = _rule[2];
          }
        }
        switch(rule.name){
          case "special":
            var regex = /^(([^\^\.<>%&',;=+\(\)?$"':#@!~\]\[{}\\/`\|])*)$/;
            rule.message = rule.message || "$ cannot contain special characters！";
            var result = _value.match(regex);
            if(_value !== "" && result == null){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "required":
            if(_element.tagName.toLowerCase() === "select"){
              // 类型为select，需要时可以设置附加属性：ruleout，表示未选中的判定规则
              rule.message = rule.message || "Please choose $！";
              rule.extend = $.extend({ ruleout: "" }, rule.extend);
              if(_element.selectedIndex === -1 || _value === rule.extend.ruleout){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              }
            } else if($(_element).attr("validate-group") === "checkbox"){
              // 类型为checkbox，需要设置checkbox组的标签属性为validate-group，值为checkbox，validate-name，值为checkboxname，附加属性min表示至少选中的项数，max为最多选中的项数
              var _checkboxname = $(_element).attr("validate-name"),_form = $(_element).closest("form");
              if(_checkboxname){
                var _checkboxes = _checkboxname ? _form.length > 0 ? _form.find(":checkbox[name='"+ _checkboxname +"']:checked") : $(":checkbox[name='"+ _checkboxname +"']:checked") : $(_element).find(":checkbox:checked");
              }else{
                var _checkboxes = $(_element).find(":checkbox:checked");
              }

              rule.extend = $.extend({ min: 1 }, rule.extend);

              if(typeof rule.extend.min != "undefined" && _checkboxes.length < rule.extend.min){
                rule.message = rule.message || "$1 choose $2 $3 items!";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$1/g, _validity.name)
                  .replace(/\$2/g, "at least")
                  .replace(/\$3/g, rule.extend.min)
                  .replace(/\$/g, _validity.name);
              } else if(typeof rule.extend.max != "undefined" && _checkboxes.length > rule.extend.max){
                rule.message = rule.message || "$1 choose $2 $3 items!";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$1/g, _validity.name)
                  .replace(/\$2/g, "at most")
                  .replace(/\$3/g, rule.extend.max)
                  .replace(/\$/g, _validity.name);
              }
            } else if($(_element).attr("validate-group") === "radio"){
              // 类型为radio，需要设置radio组的标签属性为validate-group，值为radio，validate-name，值为radioname
              var _radioname = $(_element).attr("validate-name"),
                  _form = $(_element).closest("form"),
                  _radios = _radioname ?
                                  _form.length > 0 ?
                                    _form.find(":radio[name='"+ _radioname +"']:checked") :
                                    $(":radio[name='"+ _radioname +"']:checked") :
                                $(_element).find(":radio:checked");
              rule.message = rule.message || "Please choose $！";
              rule.extend = $.extend({ min: 1 }, rule.extend);
              if(_radios.length === 0){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              }
            } else {
              rule.message = rule.message || "$ cannot be empty!";
              if(/^\s*$/.test(_value)){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              }
            }
            break;
          case "number":
            rule.extend = $.extend({ "float": false }, rule.extend);
            if(_value !== ""){
              if(rule.extend.float && !/^\d+(\.\d+)?$/.test(_value)){
                rule.message = rule.message || "$ must be a number!";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              } else if(!rule.extend.float && !/^\d+$/.test(_value)){
                rule.message = rule.message || "$ must be a number!";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              } else if(typeof rule.extend.max != "undefined" && _value > rule.extend.max){
                rule.message = rule.message || "$1 cannot be greater than the $2！";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, rule.extend.max)
                  .replace(/\$1?/g, _validity.name);
                break;
              } else if(typeof rule.extend.min != "undefined" && _value < rule.extend.min){
                rule.message = rule.message || "$1 cannot be less than the $2！";
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, rule.extend.min)
                  .replace(/\$1?/g, _validity.name);
                break;
              }
            }
            break;
          case "mobile":
            rule.message = rule.message || "Incorrect $ input!";
            if(_value !== "" && !/(^\d{11}$)/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "phone":
            rule.message = rule.message || "Incorrect $ input!";
            var reg = /(^\d{7,8}$)/;
            if(_value !== "" && !reg.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }

            break;
          case "tel":
            rule.message = rule.message || "Incorrect $ input!";
            var reg = /(^\d{11}$)|((^\d{7,8}$)|(^(\d{4}|\d{3}))-(\d{7,8})$)/;
            if(_value !== "" && !reg.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }

            break;
          case "email":
            rule.message = rule.message || "Incorrect input of $ format!";
            if(_value !== "" && !/^[A-Z_a-z0-9-\.]+@([A-Z_a-z0-9-]+\.)+[a-z0-9A-Z]{2,4}$/.test($.trim(_value))){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "url":
            rule.message = rule.message || "Incorrect input of $ format!";
            if(_value !== "" && !/^(http:|https:|ftp:)\/\/[A-Za-z0-9]+\.[A-Za-z0-9]+[\/=\?%\-&_~`@[\]\':+!]*([^<>\"])*$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "length":
            rule.extend = $.extend({}, rule.extend);
            rule.message = rule.message || "$1 length cannot be $2 $3 characters!";
            if(_value !== ""){
              if(typeof rule.extend.min != "undefined" && _value.length < rule.extend.min){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, "less than")
                  .replace(/\$3/g, rule.extend.min)
                  .replace(/\$1?/g, _validity.name);
                break;
              } else if(typeof rule.extend.max != "undefined" && _value.length > rule.extend.max){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, "greater than")
                  .replace(/\$3/g, rule.extend.max)
                  .replace(/\$1?/g, _validity.name);
                break;
              }
            }
            break;
          case "reallength":
            rule.extend = $.extend({}, rule.extend);
            rule.message = rule.message || "$1 length cannot be $2 $4 characters!";
            if(_value !== ""){
              if(typeof rule.extend.min != "undefined" && _value.replace(/[\u4e00-\u9fa5]/g, "**").length/2 < rule.extend.min){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, "less than")
                  .replace(/\$3/g, rule.extend.min)
                  .replace(/\$4/g, Number(rule.extend.min)*2)
                  .replace(/\$1?/g, _validity.name);
                break;
              } else if(typeof rule.extend.max != "undefined" && _value.replace(/[\u4e00-\u9fa5]/g, "**").length/2 > rule.extend.max){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, "greater than")
                  .replace(/\$3/g, rule.extend.max)
                  .replace(/\$4/g, Number(rule.extend.max)*2)
                  .replace(/\$1?/g, _validity.name);
                break;
              }
            }
            break;
          case "cn":
            rule.message = rule.message || "$应当由汉字组成！";
            if(_value !== "" && !/^[\u4e00-\u9fa5]+$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "onlynumberletters":
            rule.message = rule.message || "$ should consist of numbers, letters or underscores!";
            if(_value !== "" && !/^[A-Za-z0-9_]+$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "flight":
            rule.message = rule.message || "$ should consist of numbers or letters!";
            if(_value !== "" && !/^[0-9A-Za-z]+[0-9]+$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "password":
            rule.message = rule.message || "$ should consist of numbers or letters!";
            if(_value !== "" && !/^[A-Za-z0-9]+$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "repeat":
            rule.extend = $.extend({ max: 5 }, rule.extend);
            rule.message = rule.message || "$1 can't input more than $2 times!";
            if(_value !== ""){
              var reg = new RegExp("(\\S)\\1{"+ rule.extend.max +",}.*","g");
              if(reg.test(_value)){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$2/g, rule.extend.max)
                  .replace(/\$1?/g, _validity.name);
                break;
              }
            }
            break;
          case "not":
            rule.extend = $.extend({ }, rule.extend);
            rule.message = rule.message || "Incorrect input of $!";
            if(_value !== ""){
              if(rule.extend.type){
                if(
                  rule.extend.type.indexOf("email") !== -1 && /[A-Z_a-z0-9-\.]+@([A-Z_a-z0-9-]+\.)+[a-z0-9A-Z]{2,4}/.test(_value) ||
                  rule.extend.type.indexOf("mobile") !== -1 && /((\(\d{2,3}\))|(\d{3}\-))?((1[345]\d{9})|(18\d{9}))/.test(_value)
                ){
                  _validity.valid = false;
                  _validity.customErrorMsg = rule.message
                    .replace(/\$1?/g, _validity.name);
                  break;
                }
              }
            }
            break;
          case "trim":
            if(_value !== ""){
              _value = _value.replace(/^\s+|\s+$/g,'');
              $(_element).val(_value);
            }
            break;
          case "parseAnsi":
            _value !== "" && $(_element).val(_value.replace(/[\uf06c\uf06e\uf075\uf0fc\uf0d8\uf0b2]\t?/g,"· "));
            break;
          case "pattern":
            rule.extend = $.extend({}, rule.extend);
            console.log(rule.extend.regex);
            if(typeof rule.extend.regex === "string") rule.extend.regex = eval('(/'+rule.extend.regex+'/)');
            rule.message = rule.message || "The $ does not meet the specification!";
            if(_value !== "" && !rule.extend.regex.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "ajax":
            var _ajax = that.config.ajax, status = true;
            if(_ajax && typeof _ajax[rule.extend] == 'object'){
              var _success = _ajax[rule.extend].success, _ajaxoptions = {
                type: "post",
                cache: false,
                async: false
              };
              $.extend(_ajaxoptions, _ajax[rule.extend]);
              if(_ajax[rule.extend].success) {
                _ajaxoptions.success = function(data){
                  status = _ajax[rule.extend].success(data);
                }
              }
              if(_ajax[rule.extend].data && typeof _ajaxoptions.data === "function") {
                _ajaxoptions.data = _ajaxoptions.data();
              }
              $.ajax(_ajaxoptions);
              if(!status){
                _validity.valid = false;
                _validity.customErrorMsg = rule.message
                  .replace(/\$/g, _validity.name);
              }
            }
            break;
          case "repassword":
            var _passwordlist = $(_element).closest("form").find("input:password"),
                _password = _passwordlist.not(":last").filter(":last");
            rule.message = rule.message || "The two passwords are inconsistent!";
            if(_value !== "" && _password.length === 1 && _password.val() !== _value){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$2/g, _password.attr("validate-title") || "")
                .replace(/\$1/g, _validity.name);
            }
            break;
          case "equalto":
            var _equalto = $(_element).closest("form").find('[name="'+rule.extend.name+'"]');
            rule.message = rule.message || "The two passwords are inconsistent!";
            if(_value !== "" && _equalto.length === 1 && _equalto.val() !== _value){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$2/g, _equalto.attr("validate-title") || "")
                .replace(/\$1/g, _validity.name);
            }
            break;
          case "oncerequired":
            var _names = rule.extend.name.split(','),_vals = '';
            for(var i=0;i<_names.length;i++){
              _vals += $.trim($('[name="'+_names[i]+'"]',$(that.element)).val());
            }
            rule.message = rule.message || "The $2 at least one item cannot be empty!";
            if(/^\s*$/.test(_vals)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$2/g, $('[name="'+_names[0]+'"]',$(that.element)).attr("validate-title") || "")
                .replace(/\$1/g, _validity.name);
            }
            break;
          case "compareto":
            var _equalto = $(_element).closest("form").find('[name="'+rule.extend.name+'"]');
            rule.message = rule.message || "The $1 cannot be the same as $2!";
            if(_value !== "" && _equalto.length === 1 && _equalto.val() == _value){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$1/g, _validity.name)
                .replace(/\$2/g, _equalto.attr("validate-title") || "");
            }
            break;
          case "idcard":
            rule.message = rule.message || "Incorrect input $!";
            if(_value !== "" && !/^\d{17}[xX\d]$|^\d{15}$/.test(_value)){
              _validity.valid = false;
              _validity.customErrorMsg = rule.message
                .replace(/\$/g, _validity.name);
            }
            break;
          case "dynrule":
            var _dynrule = that.config.dynrule;
            _dynrule &&
              typeof _dynrule[rule.message] == 'function' &&
                that._ruleCompile(_element, _dynrule[rule.message].call(_element), _validity);
            break;
          case "dyncheck":
            var _dyncheck = that.config.dyncheck;
            if(_dyncheck && typeof _dyncheck[rule.message] == 'function'){
              var _dynresult = _dyncheck[rule.message].call(_element);
              _dynresult && _dynresult.customErrorMsg && (_dynresult.customErrorMsg = _dynresult.customErrorMsg.replace(/\$/g, _validity.name));
              $.extend(_validity, _dynresult);
            }
            break;
        }
        if(!_validity.valid) break;
      }
    },
    /*
      {
        element: $(this),
        name: "该字段",
        valid: true,
        customErrorMsg: ""
      }
    */
    validate: function(_element){
      var that = this, $element = _element || $(that.element), elements = new Array();
      if(!$element.is("[validate-rules]")) $element = $element.find("[validate-rules]");
      $element.each(function(){
        var validity = {
              element: $(this),
              name: $(this).attr("validate-title") || "This field",
              valid: true,
              customErrorMsg: ""
            },
            rules = new Array();
        try{ rules = eval($(this).attr("validate-rules") || []); }catch(e){ };  //does eval has bug ?
        that._ruleCompile(this, rules, validity);
        $(this).attr("data-valid", validity.valid).data("validity", validity);
        elements.push(validity);
      });
      return elements;
    },
    scan: function(){
      var that = this, $form = $(that.element), result = that.validate(), data = {
        valid: true
      };
      // for(var i=0; i<result.length; i++){
      //   if( typeof(that.config.skipDisabled)!='undefined' && (result[i].element.is(':disabled') || (typeof(that.config.skipDisabled)=='undefined' && result[i].element.is(':hidden')))){
      //     continue;
      //   }
      //   if(!result[i].valid) {
      //     data.firstError = result[i];
      //     data.valid = false;
      //     break;
      //   }
      // }

      for(var i=0; i<result.length; i++){
        if(typeof(that.config.skipDisabled)!='undefined' && (result[i].element.is(':disabled') || result[i].element.is(':hidden'))){
          continue;
        }
        if(!result[i].valid) {
          data.firstError = result[i];
          data.valid = false;
          break;
        }
      }

      data.result = result;
      that.config.scan && that.config.scan.call($form, data);
      return data;
    },
    option: function(){
      var that = this;
      if(arguments.length === 0) return that.config;
      if(arguments.length === 1) return that.config[arguments[0]];
      that.config[arguments[0]] = arguments[1];
      return that;
    },
    destroy: function(){
      var that = this;
      delete list[that.id];
    }
  };
  zmvalid.fn._init.prototype = zmvalid.fn;
  zmvalid.defaults = {
    scan: null,
    success: false
  };
  $.fn[ns] = function(){
    return zmvalid.apply(this, arguments);
  };
})(window.jQuery, window);
